iT邦幫忙

2025 iThome 鐵人賽

DAY 23
0
自我挑戰組

《轉職學習日記:JavaScript × Node.js × TypeScript × Docker × AWS ECS》系列 第 23

Day23 - 持續成長學習藍圖 - TypeScript(DTO 與 class-validator)

  • 分享至 

  • xImage
  •  

今天要讓這些 DTO(Data Transfer Object)真正發揮作用
搭配 class-validatorclass-transformer,在 Express 裡自動驗證請求資料。

這一天的重點是:讓「型別」不只是寫給自己看,而是能真的守護 API。


1. 什麼是 DTO?

DTO(Data Transfer Object)意思是「資料傳輸物件」,
它的任務很單純:在進入後端邏輯之前,幫你驗證和轉換資料

例子:

POST /todos
{
  "task": "寫鐵人賽 Day 23",
  "done": false
}

以前我們會手動檢查:

if (!req.body.task) return res.status(400).json({ error: "task 必填" });

但有了 TypeScript + class-validator,這些檢查能自動完成。


2. 安裝必要套件

npm install class-validator class-transformer reflect-metadata

tsconfig.json 中要開啟幾個設定:

{
  "experimentalDecorators": true,
  "emitDecoratorMetadata": true
}

3. 建立 DTO class

src/dto/create-todo.dto.ts

import { IsString, IsOptional, Length } from "class-validator";

export class CreateTodoDto {
  @IsString()
  @Length(1, 50, { message: "task 長度必須在 1~50 之間" })
  task: string;

  @IsOptional()
  @IsString()
  note?: string;
}

這裡用了幾個 decorator:

  • @IsString():確認是字串
  • @Length(min, max):限制字串長度
  • @IsOptional():允許欄位不出現

4. 寫一個驗證 Middleware

建立 src/middleware/validate-dto.ts

import { plainToInstance } from "class-transformer";
import { validate } from "class-validator";
import { Request, Response, NextFunction } from "express";

export function validateDto(dtoClass: any) {
  return async (req: Request, res: Response, next: NextFunction) => {
    const instance = plainToInstance(dtoClass, req.body);
    const errors = await validate(instance);

    if (errors.length > 0) {
      const messages = errors.map(err => Object.values(err.constraints || {})).flat();
      return res.status(400).json({ errors: messages });
    }

    next();
  };
}

這個 Middleware 會:

  1. 把 JSON 請求轉成 DTO class 實例
  2. 檢查每個欄位是否符合 decorator 規則
  3. 有錯誤就回傳 400,不讓程式繼續

5. 在 Express 路由中使用

src/index.ts

import "reflect-metadata";
import express from "express";
import { validateDto } from "./middleware/validate-dto";
import { CreateTodoDto } from "./dto/create-todo.dto";

const app = express();
app.use(express.json());

app.post("/todos", validateDto(CreateTodoDto), (req, res) => {
  const { task, note } = req.body;
  res.json({ message: "Todo 已建立", data: { task, note } });
});

app.listen(3000, () => {
  console.log("🚀 Server running on http://localhost:3000");
});

6. 測試效果

✅ 正確請求

curl -X POST http://localhost:3000/todos \
  -H "Content-Type: application/json" \
  -d '{"task": "學 TypeScript 驗證", "note": "Day 23"}'

輸出:

{
  "message": "Todo 已建立",
  "data": { "task": "學 TypeScript 驗證", "note": "Day 23" }
}

❌ 錯誤請求

curl -X POST http://localhost:3000/todos \
  -H "Content-Type: application/json" \
  -d '{"task": ""}'

輸出:

{
  "errors": ["task 長度必須在 1~50 之間"]
}

是不是很像 NestJS 那種「自動驗證」?
其實這就是 NestJS 背後的機制,只是這裡是手動加進 Express 的版本。


7. Bonus:更新 Todo 時的部分驗證

假設要做 PATCH /todos/:id,可以這樣定義:

import { IsOptional, IsString, IsBoolean } from "class-validator";

export class UpdateTodoDto {
  @IsOptional()
  @IsString()
  task?: string;

  @IsOptional()
  @IsBoolean()
  done?: boolean;
}

然後在路由:

import { UpdateTodoDto } from "./dto/update-todo.dto";

app.patch("/todos/:id", validateDto(UpdateTodoDto), (req, res) => {
  res.json({ message: "Todo 已更新", data: req.body });
});

🎯 學習心得 / 今日收穫

今天的重點是「讓 TypeScript 的型別進一步變成行為」。
我學會了:

  • class-validator 能用裝飾器幫我驗證請求資料
  • class-transformer 把普通物件轉成 class 實例
  • 自製 Middleware 讓 Express API 驗證流程自動化

以前要自己 if (!req.body.task) 一行行檢查,現在只要定義 DTO 規則,
整個驗證就變成模組化又乾淨。

而且這樣的結構在之後加上 Prisma、TypeORM 時也能直接套用。


上一篇
Day22 - 持續成長學習藍圖 - TypeScript(泛型與 Utility Types)
下一篇
Day24 - 持續成長學習藍圖 - TypeScript(Prisma / TypeORM 基礎)
系列文
《轉職學習日記:JavaScript × Node.js × TypeScript × Docker × AWS ECS》24
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言